#include <gfx/image.h>
#include <string.h>
#include <IL/il.h>
#include <IL/ilu.h>
#include <assert.h>

ceImage::ceImage()
	: CE_ASSET_SUPER ()
	, _currentLayer (0)
{
	CE_OBJECT_CONSTR
  Layer l;
  memset(&l, 0, sizeof (Layer));
  _layers.push_back(l);
}

ceImage::~ceImage()
{
  Clear ();
}

void ceImage::Clear (unsigned layer)
{
  for (unsigned i=layer, j=_layers.size(); i<j; i++)
    {
      if (_layers[i].Data)
        {
          delete [] _layers[i].Data;
        }
    }

  while (_layers.size() > layer)
    {
      _layers.pop_back();
    }
  _currentLayer = 0;
}

bool ceImage::Initialize(const Description &desc, const void *data)
{
  Clear ();
  Layer l;
  memset(&l, 0, sizeof (Layer));

  if (desc.width == 0 || desc.height == 0)
    {
      return false;
    }

  unsigned depth = 0;
  switch (desc.format)
    {
    case PF_R8G8B8:
      depth = 3;
      break;
    case PF_R8G8B8A8:
      depth = 4;
      break;
    }

  if (!depth)
    {
      return false;
    }

  unsigned size = desc.width * desc.height * depth;
  l.Data = new unsigned char [size];

  if (l.Data)
    {
      memcpy (l.Data, data, size);
    }

  l.Desc = desc;
  _layers.push_back(l);
  return true;
}

void ceImage::SetCurrentLayer(unsigned layer)
{
  assert (_layers.size() > 0);

  if (layer >= _layers.size()) layer = _layers.size() - 1;
  _currentLayer = layer;
}

unsigned ceImage::GetCurrentLayer()
{
  return _currentLayer;
}

unsigned ceImage::GetNumberOfLayers()
{
  return _layers.size();
}

void* ceImage::GetData()
{
  return _layers[_currentLayer].Data;
}

const void* ceImage::GetData() const
{
  return _layers[_currentLayer].Data;
}

const cePixelFormat& ceImage::GetFormat () const
{
  return _layers[_currentLayer].Desc.format;
}

unsigned ceImage::GetWidth() const
{
  return _layers[_currentLayer].Desc.width;
}

unsigned ceImage::GetHeight() const
{
  return _layers[_currentLayer].Desc.height;
}


unsigned ceImage::GetRGB(unsigned x, unsigned y) const
{
  unsigned d = 0;
  unsigned m = 0;
  const Layer& l = _layers[_currentLayer];
  switch (l.Desc.format)
    {
    case PF_R8G8B8:
      d = 3;
      m = 0xffffff00;
      break;
    case PF_R8G8B8A8:
      d = 4;
      m = 0xffffffff;
      break;
    }
  if (d == 0 || m == 0)
    {
      return 0x00;
    }

  const unsigned* ptr = reinterpret_cast<const unsigned*>(&l.Data[(y * l.Desc.width + x) * d]);
  return *ptr;
}

namespace
{
unsigned get_bpp (cePixelFormat format)
{
  unsigned bpp = 3;
  switch (format)
    {
    case PF_R8G8B8:
      bpp = 3;
      break;

    case PF_R8G8B8A8:
      bpp = 4;
      break;

    case PF_D24:
      bpp = 3;
      break;

    case PF_D24S8:
      bpp = 4;
      break;

    case PF_RFGFBF:
      bpp = 16;
      break;
    }

  return bpp;
}

unsigned get_size (ceImage::Description desc)
{

  return get_bpp (desc.format) * desc.width * desc.height;
}

#define ORIG(x,y,p) origData[((y) * desc.width + (x)) * bpp + (p)]
#define DEST(x,y,p) destData[((y) * dscl + (x)) * bpp + (p)]

void scale_half (const ceImage::Description& desc, const unsigned char* origData, unsigned char* destData)
{
  unsigned bpp = get_bpp(desc.format);
  unsigned dscl = desc.width / 2;
  for (unsigned oy=0, dy=0; oy<desc.width; oy+=2, dy++)
    {
      for (unsigned ox=0, dx=0; ox<desc.height; ox+=2, dx++)
        {
          for (unsigned p=0; p<bpp; p++)
            {
              unsigned v =
                  ORIG(ox+0, oy+0, p) +
                  ORIG(ox+0, oy+1, p) +
                  ORIG(ox+1, oy+0, p) +
                  ORIG(ox+1, oy+1, p);
              DEST(dx,dy,p) = (unsigned char)((v / 4) & 0xff);
            }
        }
    }
}

#undef ORIG
#undef DEST

}

void ceImage::GenerateMipMaps()
{
  Clear (1);

  unsigned w = _layers[0].Desc.width / 2;
  unsigned h = _layers[0].Desc.height / 2;
  cePixelFormat f = _layers[0].Desc.format;

  unsigned ll = 0;
  while (true)
    {
      Layer l;
      memset(&l, 0, sizeof (Layer));
      l.Desc.width = w;
      l.Desc.height = h;
      l.Desc.format = f;
			size_t size = get_size(l.Desc);
      l.Data = new unsigned char [get_size(l.Desc)];
      scale_half (_layers[ll].Desc, _layers[ll].Data, l.Data);
      _layers.push_back(l);
      ll++;

      if (w == 1 && h == 1)
        {
          break;
        }
      if (w > 1) w = w / 2;
      if (h > 1) h = h / 2;
    }

}
